<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        // 执行上下文栈
        // 案例1
        function f1() {
            var n = 999;
            function f2() {
                console.log(n++);
            }
            return f2;
        }
        var result = f1();
        result(); //输出多少？999 先输出在加一
        result(); //输出多少？1000
        // 先f1入栈,将f2返回给result,f1出栈
        // 调用result()等价于f2()
        // 然后f2入栈，输出n(999),n++(1000),f2出栈
        // 再次调用result(),f2入栈，输出n(1000),n++(1001),f2出栈
        //  当一个函数调用时，一个新的执行上下文就会被创建。
        //  都是创建新的

        // 为什么f1调用完了n不释放掉
        // 因为f1调用返回f2，而result引用f2,使用着n
        // 所有n常驻内存，不释放

        // 可以实现外部访问内部的局部变量
        // 单例模式也可以实现

        var result2 = f1();
        result2(); //999
        // 创建了新的执行上下文环境

        // 案例2
        var color = "blue";
        function changeColor() {
            var anotherColor = "red";
            function swapColors() {
                var tempColor = anotherColor;
                anotherColor = color;
                color = tempColor;
            }
            swapColors();
        }
        changeColor();
        // color = 'red'

        // var关键字
        function abc(){
            var a=1;
            console.log(a);
        }
        abc(); //1

        function abc(){
            a=1; //没有加关键字最终定义成全局变量 可能会造成全局变量的覆盖更改问题
            console.log(a);//1
        }
        abc()
        console.log(a);//1
        window.a;//1

        // 执行上下文生命周期 ：创建 执行 等待回收(全局不会被回收)
        //  创建阶段 ：生成变量对象、建立作用域链(因此f2能访问f1内的参数)、确定this指向(this不传递)
        function f1(){
            var a=1;
            function f2(){
                var b=2;
                console.log("f2内访问a,b:",a,b);
            }
            f2();
            console.log("f1外层访问a,b:",a,b); //报错 访问不到 Uncaught ReferenceError: b is not defined at f1 
        }
        f1();

        var person = {
            name :"Jack",
            show:function(){
                console.log("this_1:",this); //{name: 'Jack', show: ƒ}
                function inner(){
                    console.log("this_2:",this); //Window
                }
                inner();
            }
        }
        person.show();

        // 解决函数内部函数的this指向问题：
        // 1.使用call bind 方法解决
        var person = {
            name :"Jack",
            show:function(){
                console.log("this_1:",this); //{name: 'Jack', show: ƒ}
                function inner(){
                    console.log("this_2:",this); //Window
                }
                inner.call(this); //this 是与console.log("this_1:",this);同级的 即调用show方法的对象 
                inner.bind(this)(); 
            }
        }
        person.show();
        // 2.使用变量that解决this问题
        var person = {
            name :"Jack",
            show:function(){
                console.log("this_1:",this); //{name: 'Jack', show: ƒ}
                var that = this;
                function inner(){
                    console.log("this_2:",this); //Window
                    console.log("this_3:",that); //{name: 'Jack', show: ƒ}
                }
                
            }
        }
        person.show();

        //  return 语句的作用：返回值、终止函数的执行( 销毁当前执行上下文，弹出执行上下文栈)

        // 变量对象
        function foo() {
            var a = 20;
            function bar() {
                a = 30;
                console.log(a); //30
            }
            bar();
        }
        foo();
        //变量对象分析
        fooEC.VO = {
            arguments,
            a,
            bar
        };
        barEC.VO = {
            arguments
        };
        // foo 变量对象中的 a 的值是多少30
        // 函数 bar 的变量对象否包含了变量 a

        var a = 10,
            b = 20;

        function fn() {
            var a = 100, 
                c = 200; //逗号一条语句

            function bar() {
                var a = 500,   //如果把,改成; 则下面能输出d,d为全局变量
                    d = 600;  
                    console.log(a,b,c,d)
            }
            // console.log(a,b,c,d)  //报错d未定义
            bar();
        }
        fn(); //500 20 200 600

        // 词法作用域（静态性)是由函数定义的书写位置决定的，与调用位置无关
        function foo(){
            console.log(a)
        }
        function bar(){
            var a =3;
            foo();
        }
        var a = 2;
        bar(); //2

        function bar(){
            var a =3;
            function foo(){
                console.log(a)
            }
            foo();
        }
        var a = 2;
        bar(); //3

        // 补充：
        var scope = "global";
        function checkScope() {
            var scope = "local";
            return function () {
                return scope;
            }
        }
        console.log(checkScope()()); //local

        //new Function()创建一个匿名函数
        //不遵从静态词法作用域
        //总是在全局作用域中执行
        var scope = "global";
        function checkScope() {
            var scope = "local";
            return new Function("return scope;");
        }
        console.log(checkScope()()); //global

        var x = 10;
        function foo() {
            console.log(x);
        }
        foo(); // 10
        function fun() {
            var x = 20;
            var foo1 = foo;
            foo1(); // 10还是20？ //10
        }
        fun();

        //[[scope]] 属性——虚拟属性，无法访问和修改
        // 函数创建（定义）时生成的属性，保存着这个函数所有父级执行上下文环境中的变量对象的集合 

        // 作用域链（Scope Chain）由当前执行环境与所有父级执行环境的一系列变量对象组成
        //  提供对变量和函数访问的权限和顺序的规则
        // ——ppt的图
        
        // 变量函数访问（遮蔽效应）（链式查找）
        var a = 1;
        function fn() {
            var a = 10;
            function fn1() {
                var a = 20;
                console.log(a);
            }
            function fn2() {
                a++;
                console.log(a);
            }
            fn1(); //20
            fn2(); //11
            fn1(); //20
            fn2(); //12
        }
        fn();
        console.log(a); //1

        var a = 1;
        function fn() {
            var a = 10;
            function fn1() {
                a = 20;
                console.log(a);
            }
            function fn2() {
                a++;
                console.log(a);
            }
            fn1(); //20
            fn2(); //21
            fn1(); //20 新的执行上下文环境
            fn2(); //21
        }
        fn();
        console.log(a); //1

    </script>
</body>
</html>